<?php
/**
 * paypal 支付的配置
 * Author: lyun
 * Date: 2018/4/20
 * Created by Panxsoft.
 */

namespace App\Services\Omnipay;

use App\Exceptions\ApiErrCode;
use App\Exceptions\ApiException;
use App\Http\Controllers\Api\V1\App\Order\PayNotifyController;
use App\Models\Currency;
use App\Models\Extension\Extension;
use App\Models\Order\Order;
use App\Models\Setting;
use App\Services\Orders\PayNotifyService;
use App\Services\Utils\ApiChecker;
use PayPal\Api\Amount;
use PayPal\Api\Details;
use PayPal\Api\Item;
use PayPal\Api\ItemList;
use PayPal\Api\Payer;
use PayPal\Api\Payment;
use PayPal\Api\PaymentExecution;
use PayPal\Api\RedirectUrls;
use PayPal\Api\Transaction;
use PayPal\Auth\OAuthTokenCredential;
use PayPal\Exception\PayPalConnectionException;
use PayPal\Rest\ApiContext;

class Paypal
{
    use ApiChecker;

    const SANDBOX = 'sandbox';
    const LIVE = 'live';

    const SANDBOX_URL = 'https://www.sandbox.paypal.com/cgi-bin/webscr';
    const LIVE_URL = 'https://www.paypal.com/cgi-bin/webscr';

    private $pay_pal_mode; // pay_pal支付的模式 sandbox或live

    /**
     * Paypal constructor.
     */
    public function __construct()
    {
        $this->pay_pal_mode = Setting::getPayPalMode() ? self::SANDBOX : self::LIVE;
    }

    /**
     * @return bool|ApiContext
     */
    public function getPayPal()
    {
        if ($this->pay_pal_mode === self::SANDBOX) {
            $apiContext = new ApiContext(
                new OAuthTokenCredential(
                    config('pkx.pay_pal.sandbox.pay_pal_client_id'),
                    config('pkx.pay_pal.sandbox.pay_pal_client_secret')
                ));
        } elseif ($this->pay_pal_mode == self::LIVE) {
            $apiContext = new ApiContext(
                new OAuthTokenCredential(
                    config('pkx.pay_pal.live.pay_pal_client_id'),
                    config('pkx.pay_pal.live.pay_pal_client_secret')
                ));
            $apiContext->setConfig(['mode' => 'live']);
        } else {
            $apiContext = false;
        }

        return $apiContext;
    }

    /**
     * @caution shipping, sub_total, tax必须填写并且统计的金额等于 total
     * @link - https://developer.paypal.com/docs/api/quickstart/payments/#define-payment
     *
     * @param Order $order
     * @return null|string
     * @throws ApiException
     * @throws \Exception
     */
    public function payPalApp(Order $order)
    {
        $product = $order->orderProducts->first()->name ?? '';

        $currency_code = $order->currency_code;
            $this->checkUnless($currency_code === Currency::CODE_CNY, trans('order.paypal_use_usd'));

        $total = self::getAmount($order);

        $payer = new Payer();
        $payer->setPaymentMethod('paypal');

        $item = new Item();
        $item->setName($product)
            ->setCurrency($currency_code)
            ->setQuantity(1)
            ->setPrice($total);

        $itemList = new ItemList();
        $itemList->setItems([$item]);

        $details = new Details();
        $details->setShipping(0)
            ->setSubtotal($total)
            ->setTax(0);

        $amount = new Amount();
        $amount->setCurrency($currency_code)
            ->setTotal($total)
            ->setDetails($details);

        $transaction = new Transaction();
        $transaction->setAmount($amount)
            ->setItemList($itemList)
            ->setDescription("支付描述内容")
            ->setInvoiceNumber($order->trade_no);

        $redirectUrls = new RedirectUrls();
        $redirectUrls
            ->setReturnUrl(route(PayNotifyController::ROUTE_PAY_NOTIFY_GATEWAY_NAME, [Extension::CODE_PAYMENT_PAYPAL]).'?success=true')
            ->setCancelUrl(route(PayNotifyController::ROUTE_PAY_NOTIFY_GATEWAY_NAME, [Extension::CODE_PAYMENT_PAYPAL]).'?success=false');

        $payment = new Payment();
        $payment->setIntent('sale')
            ->setPayer($payer)
            ->setRedirectUrls($redirectUrls)
            ->setTransactions([$transaction]);

        try {
            $payment->create($this->getPayPal());
        } catch (PayPalConnectionException $e) {
            logger($e->getData());
            throw new ApiException(ApiErrCode::OPERATE_FAILED, $e->getData());
        }

        $approvalUrl = $payment->getApprovalLink();

        return $approvalUrl;
    }

    /**
     * @param Order $order
     * @return mixed
     */
    private static function getAmount(Order $order)
    {
        $currency_code = $order->currency_code;
        $currency = Currency::getCurrencyByCode($currency_code);

//        $total_amount = Currency::getProductTotalPriceByCurrency($order->currency_code, $order->orderTotalByTotal->value);
        $total_amount = round($order->total * $order->currency_value, $currency->decimal_place);

        return (string)$total_amount;
    }

    /**
     * 同步处理PayPal回调
     *
     * @link https://developer.paypal.com/docs/api/quickstart/payments/#execute-payment
     *
     * success 返回数据格式：
     * @see MockData::getPayPalSyncNotify()
     *
     * @return bool
     * @throws \Exception
     */
    public function payPalNotify()
    {
        logger($_GET);
        logger($_POST);

        $apiContext = $this->getPayPal();

        $paymentID = $_REQUEST['paymentId'] ?? null;
        $payerId = $_REQUEST['PayerID'] ?? null;
        $success = $_REQUEST['success'] ?? null;

        $data = ['code' => ApiErrCode::API_CODE_OK];

        // 取消支付
        if ($success === 'false') {
            $data = ['code' => ApiErrCode::PAY_PAL_PAYMENT_CANCEL];
            return redirect(route(PayNotifyController::ROUTE_PAY_PAL_REDIRECT_URL, $data));
        }

        try {
            $this->checkPayPalParameter(!($paymentID && $payerId));

            $payment = Payment::get($paymentID, $apiContext);

            $execute = new PaymentExecution();
            $execute->setPayerId($payerId);

            // 服务端调用 execute方法来确认支付，此处非常重要，否则PayPal会默认付款取消，此处为确认支付，商家才可以收到付款
            $payment->execute($execute, $apiContext);

            $trade_no = $payment->transactions[0]->invoice_number;

            /** @var Order $order */
            $order = Order::findOrderUnpaidByTradeNo($trade_no);

            (new PayNotifyService)->savePaidStatus($order);

        } catch (\Exception $e) {
            logger($e->getMessage());
            $data = ['code' => ApiErrCode::OPERATE_FAILED];
        }

        return redirect(route(PayNotifyController::ROUTE_PAY_PAL_REDIRECT_URL, $data));
    }

    /**
     * @param $success
     * @return bool
     * @throws ApiException
     * @throws \App\Exceptions\ApiSilentException
     */
    public function checkPayPalParameter($success)
    {
        if ($success) {
            throw new ApiException(ApiErrCode::OPERATE_FAILED, trans('order.pay_pal_error'));
        } else {
            return true;
        }
    }

    /**
     * 异步处理PayPal回调，必须后台设置ipn回调地址
     * @see MockData::getPayPalExpressNotify()
     *
     * @return string
     */
    public function payPalAsyncNotify()
    {
        $verify = $this->verifyNotify();
        if ($verify) {
            return 'success';
        } else {
            return 'fail';
        }
    }

    /**
     * @return bool
     */
    private function verifyNotify()
    {
        if (!request()->isMethod('POST')) die();
        $post = request()->all();

        $trade_no = $post['invoice']; //收取订单号

        $url = $this->pay_pal_mode === self::SANDBOX ? self::SANDBOX_URL : self::LIVE_URL;

        $data['cmd'] = '_notify-validate'; //增加cmd参数，
        foreach ($post as $key => $item) $data[$key] = ($item);  //如果数据验证失败，请在这里将参数urlencode

        $res = $this->http($url, $data, 'POST');

        if (!empty($res)) {
            if (strcmp($res, "VERIFIED") == 0) {

                if ($post['payment_status'] == 'Completed') {
                    //付款完成，这里修改订单状态
                    return true;
                }
            } elseif (strcmp($res, "INVALID") == 0) {
                //未通过认证，有可能是编码错误或非法的 POST 信息
                return false;
            }
        } else {
            //未通过认证，有可能是编码错误或非法的 POST 信息

            return false;

        }
        return false;
    }

    /**
     * 模拟提交参数，支持https提交 可用于各类api请求
     * @param string $url ： 提交的地址
     * @param array $data : POST数组
     * @param string $method : POST/GET，默认GET方式
     * @return mixed
     */
    private function http($url, $data = [], $method = 'GET')
    {
        $curl = curl_init(); // 启动一个CURL会话
        curl_setopt($curl, CURLOPT_URL, $url); // 要访问的地址
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 对认证证书来源的检查
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); // 从证书中检查SSL加密算法是否存在
        curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // 模拟用户使用的浏览器
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转
        curl_setopt($curl, CURLOPT_AUTOREFERER, 1); // 自动设置Referer
        if ($method == 'POST') {
            curl_setopt($curl, CURLOPT_POST, 1); // 发送一个常规的Post请求
            if ($data != '') {
                curl_setopt($curl, CURLOPT_POSTFIELDS, $data); // Post提交的数据包
            }
        }
        curl_setopt($curl, CURLOPT_TIMEOUT, 30); // 设置超时限制防止死循环
        curl_setopt($curl, CURLOPT_HEADER, 0); // 显示返回的Header区域内容
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 获取的信息以文件流的形式返回
        $tmpInfo = curl_exec($curl); // 执行操作
        curl_close($curl); // 关闭CURL会话

        return $tmpInfo; // 返回数据
    }
}
